{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "init_cell": true
   },
   "outputs": [],
   "source": [
    "%logstop\n",
    "%logstart -ortq ~/.logs/vc.py append\n",
    "%matplotlib inline\n",
    "import matplotlib\n",
    "import seaborn as sns\n",
    "sns.set()\n",
    "matplotlib.rcParams['figure.dpi'] = 144"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "from static_grader import grader"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Object-oriented exercises\n",
    "## Introduction\n",
    "\n",
    "The objective of these exercises is to develop your familiarity with Python's `class` syntax and object-oriented programming. By deepening our understanding of Python objects, we will be better prepared to work with complex data structures and machine learning models. We will develop a `Point` class capable of handling some simple linear algebra operations in 2D."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 1: `point_repr`\n",
    "\n",
    "The first step in defining most classes is to define their `__init__` and `__repr__` methods so that we can construct and represent distinct objects of that class. Our `Point` class should accept two arguments, `x` and `y`, and be represented by a string `'Point(x, y)'` with appropriate values for `x` and `y`.\n",
    "\n",
    "When you've written a `Point` class capable of this, execute the cell with `grader.score` for this question (do not edit that cell; you only need to modify the `Point` class)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Point(object):\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "\n",
    "    def __repr__(self):\n",
    "        return \"Point(%s, %s)\" % (self.x, self.y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================\n",
      "Your score:  1.0\n",
      "==================\n"
     ]
    }
   ],
   "source": [
    "grader.score.vc__point_repr(lambda points: [str(Point(*point)) for point in points])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 2: add_subtract\n",
    "\n",
    "The most basic vector operations we want our `Point` object to handle are addition and subtraction. For two points $(x_1, y_1) + (x_2, y_2) = (x_1 + x_2, y_1 + y_2)$ and similarly for subtraction. Implement a method within `Point` that allows two `Point` objects to be added together using the `+` operator, and likewise for subtraction. Once this is done, execute the `grader.score` cell for this question (do not edit that cell; you only need to modify the `Point` class.)\n",
    "\n",
    "(Remember that `__add__` and `__sub__` methods will allow us to use the `+` and `-` operators.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Point(8, 10)\n",
      "Point(-2, -2)\n"
     ]
    }
   ],
   "source": [
    "class Point(object):\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y= y\n",
    "\n",
    "    def __repr__(self):\n",
    "        return \"Point(%s, %s)\" % (self.x, self.y)\n",
    "\n",
    "    def __add__(self,another_point):\n",
    "        return Point(self.x + another_point.x, self.y + another_point.y)\n",
    "\n",
    "    def __sub__(self, another_point):\n",
    "        \n",
    "        return Point(self.x - another_point.x, self.y - another_point.y)\n",
    "    \n",
    "point1 = Point(3, 4)\n",
    "point2 = Point(5, 6)\n",
    "\n",
    "print((point1 + point2))\n",
    "print((point1 - point2))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================\n",
      "Your score:  1.0\n",
      "==================\n"
     ]
    }
   ],
   "source": [
    "from functools import reduce\n",
    "def add_sub_results(points):\n",
    "    points = [Point(*point) for point in points]\n",
    "    return [str(reduce(lambda x, y: x + y, points)), \n",
    "            str(reduce(lambda x, y: x - y, points))]\n",
    "\n",
    "grader.score.vc__add_subtract(add_sub_results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 3: multiplication\n",
    "\n",
    "Within linear algebra there's many different kinds of multiplication: scalar multiplication, inner product, cross product, and matrix product. We're going to implement scalar multiplication and the inner product.\n",
    "\n",
    "We can define scalar multiplication given a point $P$ and a scalar $a$ as \n",
    "$$aP=a(x,y)=(ax,ay)$$\n",
    "\n",
    "and we can define the inner product for points $P,Q$ as\n",
    "$$P\\cdot Q=(x_1,y_1)\\cdot (x_2, y_2) = x_1x_2 + y_1y_2$$\n",
    "\n",
    "To test that you've implemented this correctly, compute $2(x, y) \\cdot (x, y)$ for a `Point` object. Once this is done, execute the `grader.score` cell for this question (do not edit that cell; you only need to modify the `Point` class.)\n",
    "\n",
    "(Remember that `__mul__` method will allow us to use the `*` operator. Also don't forget that the ordering of operands matters when implementing these operators.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "39\n"
     ]
    }
   ],
   "source": [
    "class Point(object):\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "\n",
    "    def __repr__(self):\n",
    "        return \"Point(%s, %s)\" % (self.x, self.y)\n",
    "\n",
    "    def __mul__(self, point2, scalar=False):\n",
    "        return (self.x * point2.x) + (self.y * point2.y)\n",
    "\n",
    "\n",
    "point0 = Point(3, 4)\n",
    "point1 = Point(5, 6)\n",
    "\n",
    "print(point0 * point1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================\n",
      "Your score:  1.0\n",
      "==================\n"
     ]
    }
   ],
   "source": [
    "def mult_result(points):\n",
    "    points = [Point(*point) for point in points]\n",
    "    return [point*point*2 for point in points]\n",
    "\n",
    "grader.score.vc__multiplication(mult_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 4: Distance\n",
    "\n",
    "Another quantity we might want to compute is the distance between two points.  This is generally given for points $P_1=(x_1,y_1)$ and $P_2=(x_2,y_2)$ as \n",
    "$$D = |P_2 - P_1| = \\sqrt{(x_1-x_2)^2 + (y_1-y_2)^2}.$$\n",
    "\n",
    "Implement a method called `distance` which finds the distance from a point to another point. \n",
    "\n",
    "Once this is done, execute the `grader.score` cell for this question (do not edit that cell; you only need to modify the `Point` class.)\n",
    "\n",
    "### Hint\n",
    "* *You can use the `sqrt` function from the math package*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    }
   ],
   "source": [
    "import math\n",
    "\n",
    "class Point(object):\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "\n",
    "    def __repr__(self):\n",
    "        return \"Point(%s, %s)\" % (self.x, self.y)\n",
    "    \n",
    "#     def __lt__(self, other):\n",
    "#         if isinstance (other, Point):\n",
    "#             return self < other\n",
    "\n",
    "    def distance(self, other):\n",
    "        if isinstance (other, Point):\n",
    "            return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)\n",
    "\n",
    "\n",
    "point1 = Point(3, 4)\n",
    "point2 = Point(13, 24)\n",
    "\n",
    "point3 = Point(5,6)\n",
    "point4 = Point (10,61)\n",
    "\n",
    "\n",
    "#print(point1.distance(point2))\n",
    "#print(point3.distance(point4))\n",
    "\n",
    "k= point1.distance(point2)\n",
    "l= point3.distance(point4)\n",
    "\n",
    "print(k<l)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================\n",
      "Your score:  1.0\n",
      "==================\n"
     ]
    }
   ],
   "source": [
    "def dist_result(points):\n",
    "    points = [Point(*point) for point in points]\n",
    "    return [points[0].distance(point) for point in points]\n",
    "\n",
    "grader.score.vc__distance(dist_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 5: Algorithm\n",
    "\n",
    "Now we will use these points to solve a real world problem!  We can use our Point objects to represent measurements of two different quantities (e.g. a company's stock price and volume).  One thing we might want to do with a data set is to separate the points into groups of similar points.  Here we will implement an iterative algorithm to do this which will be a specific case of the very general $k$-means clustering algorithm.  The algorithm will require us to keep track of two clusters, each of which have a list of points and a center (which is another point, not necessarily one of the points we are clustering).  After making an initial guess at the center of the two clusters, $C_1$ and $C_2$, the steps proceed as follows\n",
    "\n",
    "1. Assign each point to $C_1$ or $C_2$ based on whether the point is closer to the center of $C_1$ or $C_2$.\n",
    "2. Recalculate the center of $C_1$ and $C_2$ based on the contained points. \n",
    "\n",
    "See [reference](https://en.wikipedia.org/wiki/K-means_clustering#Standard_algorithm) for more information.\n",
    "\n",
    "This algorithm will terminate in general when the assignments no longer change.  For this question, we would like you to initialize one cluster at `(1, 0)` and the other at `(-1, 0)`.  \n",
    "\n",
    "The returned values should be the two centers of the clusters ordered by greatest `x` value.  Please return these as a list of numeric tuples $[(x_1, y_1), (x_2, y_2)]$\n",
    "\n",
    "In order to accomplish this we will create a class called cluster which has two methods besides `__init__` which you will need to write.  The first method `update` will update the center of the Cluster given the points contained in the attribute `points`.  Remember, you after updating the center of the cluster, you will want to reassign the points and thus remove previous assignments.  The other method `add_point` will add a point to the `points` attribute.\n",
    "\n",
    "Once this is done, execute the `grader.score` cell for this question (do not edit that cell; you only need to modify the `Cluster` class and `compute_result` function.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Cluster(object):\n",
    "    def __init__(self, x, y):\n",
    "        self.center = Point(x, y)\n",
    "        self.point = Point(x, y)\n",
    "        self.points = []\n",
    "\n",
    "    # Update the centre of the point\n",
    "    def update(self):\n",
    "        sumX = 0\n",
    "        for i in range(len(self.points)):\n",
    "            sumX += self.points[i].x\n",
    "        \n",
    "        sumY = 0\n",
    "        for i in range(len(self.points)):\n",
    "            sumY += self.points[i].y\n",
    "        \n",
    "        self.center = Point(sumX / len(self.points), sumY / len(self.points))\n",
    "        self.points = []\n",
    "\n",
    "    #add a point to a list of points\n",
    "    def add_point(self, point):\n",
    "        return self.points.append(point)\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_result(points):\n",
    "    points = [Point(*point) for point in points]\n",
    "    a = Cluster(1,0)\n",
    "    b = Cluster(-1,0)\n",
    "        \n",
    "    a_old = []\n",
    "    for _ in range(10000): # max iterations\n",
    "        for point in points:\n",
    "            if point.distance(a.center) < point.distance(b.center):\n",
    "                a.add_point(point)\n",
    "            else:\n",
    "                b.add_point(point)\n",
    "                \n",
    "        if a_old == a.points:\n",
    "            break\n",
    "            \n",
    "        a_old = a.points\n",
    "        a.update()\n",
    "        b.update()\n",
    "    if a.center.x > b.center.x:\n",
    "        return [(a.center.x, a.center.y),(b.center.x, b.center.y)]\n",
    "    else:\n",
    "        return [(b.center.x, b.center.y),(a.center.x, a.center.y)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================\n",
      "Your score:  1.0\n",
      "==================\n"
     ]
    }
   ],
   "source": [
    "grader.score.vc__k_means(compute_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Copyright &copy; 2019 The Data Incubator.  All rights reserved.*"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "nbclean": true
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
